using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using pub.Interop;

namespace pub.Settings
{
    public class IniSettings : ISettings
    {
        protected string m_iniFileName;
        protected string m_name;
        protected IniSettings m_parentSettings;
        protected string m_sectionName;

        public IniSettings()
        {
            var assembly = Assembly.GetEntryAssembly();
            var iniFileName = assembly.Location;
            iniFileName = iniFileName.Substring(0, iniFileName.LastIndexOf('.') + 1) + "ini";
            //todo: add builtin check write access check and appdata fallback
            init(iniFileName);
        }

        public IniSettings(String iniFileName)
        {
            init(iniFileName);
        }

        private static string GetSubSettingsSection(IniSettings parentSettings)
        {
            string sectionName;
            if (parentSettings.IsGlobal)
            {
                sectionName = parentSettings.m_sectionName;
            }
            else if (parentSettings.IsRoot)
            {
                sectionName = parentSettings.m_name;
            }
            else
            {
                sectionName = parentSettings.m_sectionName + "." + parentSettings.m_name;
            }
            return sectionName;
        }

        public IniSettings(IniSettings parentSettings, string name)
        {
            m_iniFileName = parentSettings.m_iniFileName;
            //string parentSectionName = parentSettings.m_sectionName;

            m_sectionName = GetSubSettingsSection(parentSettings);

//            m_sectionName = parentSettings.m_parentSettings == null
//                                ? parentSettings.m_sectionName
//                                : (parentSettings.m_parentSettings.m_parentSettings == null
//                                       ? parentSettings.m_name
//                                       : parentSettings.m_sectionName + "." + parentSettings.m_name);
            m_parentSettings = parentSettings;
            m_name = name;
        }

        public bool IsGlobal
        {
            get { return m_parentSettings == null; }
        }

        public bool IsRoot
        {
            get { return m_parentSettings.IsGlobal; }
        }

        public string Name
        {
            get { return m_name; }
        }

        public string IniFileName
        {
            get { return m_iniFileName; }
        }

        #region IPersistSettings Members

        public ISettings this[string name]
        {
            get { return new IniSettings(this, name); }
        }


        public bool BoolValue
        {
            get { return getIniValue().Equals("1"); }
            set { setIniValue(value ? "1" : "0"); }
        }

        public string StringValue
        {
            get { return getIniValue(); }
            set { setIniValue(value); }
        }

        public int IntValue
        {
            get { return int.Parse(getIniValue()); }
            set { setIniValue(value.ToString()); }
        }

        public T GetValue<T>(T defValue)
        {
            try
            {
                var rawValue = getIniValue();
                var type = typeof (T);
                if (type == typeof (bool))
                {
                    if (string.IsNullOrEmpty(rawValue))
                    {
                        return defValue;
                    }
                    if (rawValue == "1" ||
                        rawValue.Equals("TRUE", StringComparison.OrdinalIgnoreCase) ||
                        rawValue.Equals("YES", StringComparison.OrdinalIgnoreCase) ||
                        rawValue.Equals("ON", StringComparison.OrdinalIgnoreCase))
                    {
                        return (T) (object) true;
                    }
                    return (T) (object) false;
                }
                else
                {
                    if (rawValue == "")
                    {
                        return defValue;
                    }
                    return (T) Convert.ChangeType(rawValue, type);
                }
            }
            catch
            {
                return defValue;
            }
        }

        public IDictionary<string, string> Entries
        {
            get
            {
                var entryList = GetSectionEntryList();
                var map = new Dictionary<string, string>();
                foreach (var entry in entryList)
                {
                    map[entry.Key] = entry.Value;
                }
                return map;
            }
        }

        public void Clear()
        {
            var sectionName = GetSubSettingsSection(this);
            Win32.WritePrivateProfileString(sectionName, null, null, m_iniFileName);
        }

        public byte[] BytesValue
        {
            get { return GetBytesValue(); }
            set { SetBytesValue(value); }
        }

        private void SetBytesValue(byte[] value)
        {
            this.m_parentSettings[m_name + "_size"].IntValue = value.Length;
            Win32.WritePrivateProfileStruct(m_sectionName, m_name, value, value.Length,
                    m_iniFileName);
        }

        private byte[] GetBytesValue()
        {
            var length = this.m_parentSettings[m_name + "_size"].GetValue(0);
            if (length <= 0)
            {
                return null;
            }
            var bts = new byte[length];
            Win32.GetPrivateProfileStruct(m_sectionName, m_name, bts, (uint) bts.Length,
                    m_iniFileName);
            return bts;
        }

        #endregion

        protected void init(String iniFileName)
        {
            m_iniFileName = iniFileName;
            m_sectionName = "Global";
            m_name = "Default";
            m_parentSettings = null;
        }

        private string getIniValue()
        {
            var sb = new StringBuilder(255);
            Win32.GetPrivateProfileString(m_sectionName, m_name, "", sb, 255, m_iniFileName);
            return sb.ToString();
        }

        private void setIniValue(String value)
        {
            Win32.WritePrivateProfileString(m_sectionName, m_name, value, m_iniFileName);
        }

        //
        private const int MaxSectionSize = 32767; // 32 KB

        private List<KeyValuePair<string, string>> GetSectionEntryList()
        {
            var sectionName = GetSubSettingsSection(this);

            List<KeyValuePair<string, string>> kvps;

            var buffer = new byte[2048];

            Win32.GetPrivateProfileSection(sectionName, buffer, 2048, m_iniFileName);
            var entries = Encoding.Default.GetString(buffer).Trim('\0').Split('\0');

            kvps = new List<KeyValuePair<string, string>>(entries.Length);

            for (var i = 0; i < entries.Length; ++i)
            {
                var entry = entries[i];
                if (entry.Length == 0)
                {
                    continue;
                }

                var pos = entry.IndexOf('=');
                var key = entry.Substring(0, pos);
                var value = entry.Substring(pos + 1, entry.Length - pos - 1);

                kvps.Add(new KeyValuePair<string, string>(key, value));
            }

            return kvps;
        }
    }
}